package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author jinpei
 * @version v1.0
 * @date 2019/5/13 16:25
 * @description TODO
 **/
@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    public Goods buildGoods(SpuDTO spu) {
        // 1.spu下的所有sku
        Long spuId = spu.getId();
        List<SkuDTO> skuDTOList = itemClient.querySkuBySpuId(spuId);
        // 准备一个集合，用map来代替sku，只需要sku中的部分数据
        List<Map<String, Object>> skuList = new ArrayList<>();
        for (SkuDTO skuDTO : skuDTOList) {
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", skuDTO.getId());
            skuMap.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            skuMap.put("title", skuDTO.getTitle());
            skuMap.put("price", skuDTO.getPrice());
            skuList.add(skuMap);
        }

        // 2.sku的价格集合
        Set<Long> price = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        // 3.搜索字段，包含名称、分类、品牌等
        // 3.1查询分类名称
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(spu.getCategoryIds());
        String categoryName = categoryDTOList.stream().map(CategoryDTO::getName).collect(Collectors.joining());
        // 3.2查询品牌名称
        BrandDTO brandDTO = itemClient.queryBrandById(spu.getBrandId());
        // 3.3拼接
        String all = spu.getName() + categoryName + brandDTO.getName();

        // 4.规格参数，包括key和value
        Map<String, Object> specs = new HashMap<>();
        // 4.1 获取规格参数key，来自于SpecParam中当前分类下的需要搜索的规格
        List<SpecParamDTO> params = itemClient.querySpecParams(null, spu.getCid3(), true);
        // 4.2 获取规格参数的值，来自于spuDetail
        SpuDetailDTO spuDetail = itemClient.querySpuDetailById(spuId);
        // 4.2.1 通用规格参数值
        Map<Long, Object> genericSpec = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, Object.class);
        // 4.2.2 特有规格参数值
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });
        // 4.3存入specs
        for (SpecParamDTO param : params) {
            // 从param中取出name作为规格参数key
            String key = param.getName();
            // 从spuDetail中取出之作为value
            Object value = null;
            // 判断是否为通用规格参数还是特有规格参数
            if (param.getGeneric()) {
                // 通用规格参数
                value = genericSpec.get(param.getId());
            } else {
                // 特有规格参数
                value = specialSpec.get(param.getId());
            }
            // 判断是否是数字类型
            if(param.getNumeric()){
                // 是数字类型，分段
                value = chooseSegment(value, param);
            }
            // 添加到specs
            specs.put(key, value);

        }
        // 5.准备goods对象
        Goods goods = new Goods();
        goods.setSkus(JsonUtils.toString(skuList));
        goods.setPrice(price);
        goods.setAll(all);
        goods.setSpecs(specs);
        goods.setBrandId(spu.getBrandId());
        goods.setCategoryId(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime().getTime());
        goods.setId(spu.getId());
        goods.setSubTitle(spu.getSubTitle());
        return goods;
    }

    private String chooseSegment(Object value, SpecParamDTO param) {

        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : param.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + param.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + param.getUnit() + "以下";
                } else {
                    result = segment + param.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private ElasticsearchTemplate esTemplate;

    public PageResult<GoodsDTO> search(SearchRequest request) {
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 1. 搜索和过滤
        // 1.0.选择要返回的字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        // 1.1.组装查询条件
        QueryBuilder basicQuery =  buildBasicQuery(request);
        // 1.2.添加条件
        queryBuilder.withQuery(basicQuery);
        // 2. 分页
        int page = request.getPage() - 1;
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));
        // 3. 查询结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 4. 解析数据
        Long total = result.getTotalElements();
        Integer totalPage = result.getTotalPages();
        List<Goods> goodsList = result.getContent();
        // 转换DTO
        List<GoodsDTO> goodsDTOList = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);
        // 5. 封装并返回
        return new PageResult<>(total, totalPage, goodsDTOList);
    }

    public Map<String, List<?>> queryFilterList(SearchRequest request) {

        // 准备一个map，放过滤项的key和value的集合
        Map<String, List<?>> filterListMap = new LinkedHashMap<>();

        // 创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 1. 搜索条件
        // 1.1.组装查询条件
        QueryBuilder basicQuery = buildBasicQuery(request);
        // 1.2.添加条件
        queryBuilder.withQuery(basicQuery);
        // 1.3控制返回搜索数量
        queryBuilder.withPageable(PageRequest.of(0,1));
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));

        // 2.添加聚合
        // 2.1.添加分类的聚合
        String categoryAggName = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("categoryId"));
        // 2.2.添加品牌的聚合
        String brandAggName = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        // 3.查询聚合结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析聚合结果
        Aggregations aggregations = result.getAggregations();
        // 4.1.获取分类聚合
        Terms categoryTerms = aggregations.get(categoryAggName);
        List<Long> idList = handleCategoryTerms(categoryTerms, filterListMap);
        // 4.2.获取分类聚合
        Terms brandTerms = aggregations.get(brandAggName);
        handleBrandTerms(brandTerms, filterListMap);

        // 5.处理规格参数的集合
        if (idList != null && idList.size() == 1) {
            // 规格参数的集合
            handleSpecAgg(idList.get(0), basicQuery, filterListMap);
        }

        return filterListMap;
    }

    private void handleSpecAgg(Long cid, QueryBuilder basicQuery, Map<String, List<?>> filterListMap) {
        // 创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 1. 搜索条件
        // 1.1.添加条件
        queryBuilder.withQuery(basicQuery);
        // 1.2.控制返回搜索数量
        queryBuilder.withPageable(PageRequest.of(0,1));
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));

        // 2.查询当前分类下需要被搜索的规格参数
        List<SpecParamDTO> params = itemClient.querySpecParams(null, cid, true);

        // 3.添加聚合条件
        for (SpecParamDTO param : params) {
            // 从param中取出name作为聚合名称
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }

        // 4.查询
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 5.解析
        Aggregations aggregations = result.getAggregations();
        // 循环param，因为每个param都有自己的聚合
        for (SpecParamDTO param : params) {
            // 取出参数名称，作为聚合名称
            String name = param.getName();
            Terms terms = aggregations.get(name);
            // 取出terms中桶的值作为过滤项的待选项
            List<String> options = terms.getBuckets().stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());

            // 存入map中
            filterListMap.put(name, options);

        }
    }

    private void handleBrandTerms(Terms terms, Map<String, List<?>> filterListMap) {
        // 解析桶，取出桶中每个品牌的id，变成集合
        List<Long> idList = terms.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        // 根据id查询分类
        List<BrandDTO> brandDTOList = itemClient.queryBrandByIds(idList);
        filterListMap.put("品牌", brandDTOList);
    }

    private List<Long>  handleCategoryTerms(Terms terms, Map<String, List<?>> filterListMap) {
        /*List<? extends Terms.Bucket> buckets = terms.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            Number key = bucket.getKeyAsNumber();
            long id = key.longValue();
        }*/
        // 解析桶，取出桶中每个分类的id，变成集合
        List<Long> idList = terms.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        // 根据id查询分类
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(idList);
        filterListMap.put("分类", categoryDTOList);
        return idList;
    }

    private QueryBuilder buildBasicQuery(SearchRequest request) {
        // 构建一个boolean查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // match查询，作为搜索条件
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        // filter查询，作为过滤条件
        Map<String, String> filters = request.getFilters();
        // 遍历所有过滤项，把每个过滤项都添加到过滤条件中
        for (Map.Entry<String, String> entry : filters.entrySet()) {
            // 取出key和value作为过滤字段和过滤值
            String key = entry.getKey();
            String value = entry.getValue();

            // 要对key不同的情况做出处理
            if ("品牌".equals(key)) {
                key = "brandId";
            } else if ("分类".equals(key)) {
                key = "categoryId";
            } else {
                key = "specs." + key;
            }
            // 添加过滤项
            queryBuilder.filter(QueryBuilders.termQuery(key, value));
        }
        return queryBuilder;

    }

    public void saveGoods(Long spuId) {
        // 查询出spu
        SpuDTO spu = itemClient.querySpuById(spuId);
        // 构建出goods
        Goods goods = buildGoods(spu);
        // 新增到索引库
        goodsRepository.save(goods);
    }

    public void deleteGoods(Long spuId) {
        goodsRepository.deleteById(spuId);
    }
}
